package mini_program

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"git.mycaigou.com/gfyx/common/cache"
	"git.mycaigou.com/gfyx/micro-gfyx-user-service/infrastructure/common/constants"
	"git.mycaigou.com/gfyx/micro-gfyx-user-service/infrastructure/common/ossfile"
	"git.mycaigou.com/gfyx/micro-gfyx-user-service/infrastructure/common/utils/wxbizdatacrypt"
	"git.mycaigou.com/gfyx/micro-gfyx-user-service/infrastructure/repository/qrcode"
	"git.mycaigou.com/gfyx/micro-gfyx-user-service/infrastructure/repository/remote/wechat_api_remote"
	pb "git.mycaigou.com/gfyx/micro-gfyx-user-service/proto/micro_gfyx_user_service_proto/micro-gfyx-user-service"
	"git.myscrm.cn/golang/stark/v4"
	"time"
)

type miniProgramService struct {
	wechatApiRepo wechat_api_remote.WechatApiRepoIface
	qrcodeRepo    qrcode.QrcodeRepositoryIface
}

const mpTokenCacheKey = "wechat:mini_program:access_token"

func NewMiniProgramService(
	wechatApiRepo wechat_api_remote.WechatApiRepoIface,
	qrcodeRepo qrcode.QrcodeRepositoryIface,
) MiniProgramServiceIface {
	return &miniProgramService{
		wechatApiRepo: wechatApiRepo,
		qrcodeRepo:    qrcodeRepo,
	}
}

func (s *miniProgramService) GetAccessToken(ctx context.Context) (*pb.GetAccessTokenResponse, error) {
	//优先取缓存，其次钓微信接口获取
	cacheRedis, _ := cache.Redis()

	cacheResult, err := cacheRedis.Get(ctx, mpTokenCacheKey)
	if err != nil {
		return nil, err
	}

	result := &pb.GetAccessTokenResponse{}
	//优先取缓存，其次取数芯接口数据
	if cacheResult == "" || cacheResult == "null" {
		mpToken, err := s.wechatApiRepo.GetAccessToken(ctx)
		if err != nil {
			return nil, err
		}

		if mpToken != nil {
			bytes, _ := json.Marshal(mpToken)
			cacheTime := mpToken.ExpiresIn
			if mpToken.ExpiresIn > 200 {
				cacheTime -= 200
			}
			_, _ = cacheRedis.Set(ctx, mpTokenCacheKey, string(bytes), true, int(cacheTime))
			result = &pb.GetAccessTokenResponse{
				AccessToken: mpToken.AccessToken,
				ExpireIn:    cacheTime,
			}
		}
	} else {
		// 缓存命中
		_ = json.Unmarshal([]byte(cacheResult), result)
	}
	return result, nil
}

func (s *miniProgramService) ClearAccessTokenCache(ctx context.Context) error {
	cacheRedis, _ := cache.Redis()

	_, err := cacheRedis.Del(ctx, mpTokenCacheKey)
	return err
}

func (s *miniProgramService) GetQRCode(ctx context.Context, request *pb.GetQRCodeRequest) (*pb.GetQRCodeResponse, error) {
	//1.从本地数据库获取图片信息，若获取到了直接返回，若未获取到继续往下执行
	var isHyaline int32 = 0
	if request.IsHyaline {
		isHyaline = 1
	}
	qrcodePo, err := s.qrcodeRepo.QueryOne(ctx, map[string]interface{}{"page": request.Page, "scene": request.Scene,
		"width": request.Width, "is_hyaline": isHyaline, "is_deleted": 0})
	if err != nil {
		return nil, err
	}

	resp := &pb.GetQRCodeResponse{}
	if qrcodePo != nil {
		resp.QrCodeUrl, err = ossfile.GetSignURL(qrcodePo.OssFilePath)
		if err != nil {
			return nil, err
		}
		return resp, nil
	}

	qrcodePo, err = s.qrcodeRepo.Create(ctx, &qrcode.QrcodePo{
		Page:       request.Page,
		Scene:      request.Scene,
		Width:      request.Width,
		IsHyaline:  isHyaline,
		IsDeleted:  0,
		CreatedOn:  time.Now(),
		CreatedBy:  "",
		ModifiedOn: time.Now(),
		ModifiedBy: "",
	})
	if err != nil {
		return nil, err
	}

	//2.获取token，调用微信接口获取图片
	accessToken, err := s.GetAccessToken(ctx)
	if err != nil {
		return nil, err
	}
	//将前端传过来的scene值替换成短的值，带id，小程序的页面地址页修改为中转页的页面地址
	scene := fmt.Sprintf("%d", qrcodePo.Id)
	imageBytes, err := s.wechatApiRepo.GetUnLimitQrCode(ctx, &wechat_api_remote.GetWxUnLimitQrCodeRequest{
		Scene:     scene,
		Page:      constants.QrCodeMiddlewarePage,
		Width:     request.Width,
		IsHyaline: request.IsHyaline,
	}, accessToken.AccessToken)
	if err != nil {
		return nil, err
	}
	//3.图片文件上传至阿里云
	ossFilePath, signedURL, err := ossfile.PutOss(bytes.NewReader(imageBytes), constants.PathMiniProgramQrCode, "", ".jpeg", 1)
	if err != nil {
		return nil, err
	}
	//4.保存图片信息至数据库，并返回图片地址，结束
	_ = s.qrcodeRepo.UpdateById(ctx, qrcodePo.Id, map[string]interface{}{"oss_file_path": ossFilePath})
	resp.QrCodeUrl = signedURL
	return resp, nil
}

func (s *miniProgramService) GetMiniProgramOpenId(ctx context.Context, request *pb.GetMiniProgramOpenIdRequest) (*pb.GetMiniProgramOpenIdResponse, error) {
	data, err := s.wechatApiRepo.JsCode2Session(ctx, request.Code)
	if err != nil {
		return nil, err
	}

	resp := &pb.GetMiniProgramOpenIdResponse{}
	if data != nil {
		resp.Openid = data.OpenId
		resp.Unionid = data.UnionId
		resp.SessionKey = data.SessionKey
	}
	return resp, nil
}

func (s *miniProgramService) GetMobile(ctx context.Context, request *pb.GetMobileRequest) (string, error) {
	stark.Logger.Infof(ctx, "测试老版本微信需要走解密数据逻辑,request:%+v", request)
	if request.MobileCode != "" {
		// 新版本微信无需解密
		accessToken, err := s.GetAccessToken(ctx)
		if err != nil {
			return "", err
		}

		return s.wechatApiRepo.GetMpMobile(ctx, accessToken.AccessToken, request.MobileCode)
	} else {
		// 老版本微信需要解密数据
		appId := stark.Configer.(stark.ApolloConfigerIface).GetStringValue("gfyx_wechat_mp_appid", "")
		pc := wxbizdatacrypt.WxBizDataCrypt{AppId: appId, SessionKey: request.SessionKey}
		decrypted, err := pc.Decrypt(request.EncryptedData, request.Iv)
		stark.Logger.Info(ctx, "解密后的数据:", decrypted)
		if err != nil {
			stark.Logger.Errorf(ctx, "解密手机号参数:%+v,报错:%s", request, err.Error())
			return "", err
		}
		if decrypted.PurePhoneNumber == "" {
			stark.Logger.Errorf(ctx, "解密手机号为空:%+v,报错:%s", request, err.Error())
			return "", errors.New("获取到手机号为空")
		}

		return decrypted.PurePhoneNumber, nil
	}
}

func (s *miniProgramService) GetQrCodeInfoById(ctx context.Context, request *pb.GetQrCodeInfoByIdRequest) (*pb.GetQrCodeInfoByIdResponse, error) {
	qrcodePo, err := s.qrcodeRepo.QueryOne(ctx, map[string]interface{}{"id": request.Id, "is_deleted": constants.IsDeletedFalse})
	if err != nil {
		return nil, err
	}
	if qrcodePo == nil {
		return nil, errors.New("未查询到该二维码信息")
	}
	return &pb.GetQrCodeInfoByIdResponse{
		Id:    qrcodePo.Id,
		Page:  qrcodePo.Page,
		Scene: qrcodePo.Scene,
	}, nil
}
